package hu.cskassai.sad.business.internal;

import hu.cskassai.sad.business.GraphManager;
import hu.cskassai.sad.business.helper.DynamicFileClassLoader;
import hu.cskassai.sad.business.helper.GraphGenerator;
import hu.cskassai.sad.business.requests.ChangeAlgorithmRequest;
import hu.cskassai.sad.business.requests.ChangeGraphRequest;
import hu.cskassai.sad.business.requests.ChangeHeuristicRequest;
import hu.cskassai.sad.business.requests.SaveGraphRequest;
import hu.cskassai.sad.business.response.ChangeAlgorithmResponse;
import hu.cskassai.sad.business.response.ChangeGraphResponse;
import hu.cskassai.sad.business.response.ChangeHeuristicResponse;
import hu.cskassai.sad.business.vo.AlgorithmPropertyVO;
import hu.cskassai.sad.business.vo.EdgeVO;
import hu.cskassai.sad.business.vo.Environment;
import hu.cskassai.sad.business.vo.GraphPropertyVO;
import hu.cskassai.sad.business.vo.GraphVO;
import hu.cskassai.sad.business.vo.HeuristicPropertyVO;
import hu.cskassai.sad.business.vo.VertexVO;
import hu.cskassai.sad.external.api.Algorithm;
import hu.cskassai.sad.external.api.DynamicAlgorithm;
import hu.cskassai.sad.external.api.Heuristic;
import hu.cskassai.sad.external.api.InformedAlgorithm;
import hu.cskassai.sad.external.impl.LPA;
import hu.cskassai.sad.model.factory.DomainFactory;
import hu.cskassai.sad.model.factory.hibernate.DomainFactoryImpl;
import hu.cskassai.sad.model.graph.Edge;
import hu.cskassai.sad.model.graph.Vertex2D;

import java.awt.Color;
import java.awt.Paint;
import java.awt.geom.Point2D;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.collections15.Transformer;
import org.apache.commons.io.IOUtils;

import com.thoughtworks.xstream.XStream;

import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.UndirectedSparseGraph;
import edu.uci.ics.jung.graph.util.EdgeType;

public class GraphManagerImpl implements GraphManager<Vertex2D, Edge> {
	
	private Graph<Vertex2D, Edge> graph;
	
	private InformedAlgorithm<Vertex2D, Edge> informedAlgorithm;
	
	private DynamicAlgorithm<Vertex2D, Edge> dynamicAlgorithm;
	
	private Algorithm<Vertex2D, Edge> algorithm;
	
	private Heuristic<Vertex2D, Edge> heuristic;
	
	private final DomainFactory domainFactory = new DomainFactoryImpl();
	
	private final static String ENVIRONMENT_PATH = "environment.xml";
	
	private Environment environment = new Environment();
	
	private final Map<String, AlgorithmPropertyVO> algorithNameMap = new HashMap<String, AlgorithmPropertyVO>();
	
	private final Map<String, HeuristicPropertyVO> heuristicNameMap = new HashMap<String, HeuristicPropertyVO>();
	
	private final Map<String, GraphPropertyVO> graphNameMap = new HashMap<String, GraphPropertyVO>();
	
	public Collection<Object[]> getVertexProperties(final Vertex2D vertex) {
		return algorithm.getVertexProperties(vertex);
	}
	
	private final Transformer<Vertex2D, Point2D> vertexPointTransformer = new Transformer<Vertex2D, Point2D>() {
		
		public Point2D transform(final Vertex2D input) {
			return input.getPoint();
		}
	};
	
	public Transformer<Vertex2D, Point2D> getVertexToPointTransformer() {
		return vertexPointTransformer;
	}
	
	public void loadEnvironment() {
		FileInputStream fileInputStream;
		try {
			fileInputStream = new FileInputStream(ENVIRONMENT_PATH);
			final String xml = IOUtils.toString(fileInputStream, "UTF8");
			final XStream xStream = new XStream();
			environment = (Environment) xStream.fromXML(xml);
			for (final GraphPropertyVO graphPropertyVO : environment.getGraphs()) {
				graphNameMap.put(graphPropertyVO.getName(), graphPropertyVO);
			}
			for (final AlgorithmPropertyVO algorithmPropertyVO : environment.getAlgorithms()) {
				algorithNameMap.put(algorithmPropertyVO.getName(), algorithmPropertyVO);
			}
			for (final HeuristicPropertyVO heuristicPropertyVO : environment.getHeuristicPropertyVOs()) {
				heuristicNameMap.put(heuristicPropertyVO.getName(), heuristicPropertyVO);
			}
		} catch (final Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void saveEnvironment() {
		final XStream xStream = new XStream();
		final String string = xStream.toXML(environment);
		try {
			final byte[] content = string.getBytes("UTF-8");
			final FileOutputStream dfis = new FileOutputStream(ENVIRONMENT_PATH);
			dfis.write(content);
			dfis.close();
		} catch (final Exception e) {
			e.printStackTrace();
		}
	}
	
	public GraphManagerImpl() {
		super();
		graph = GraphGenerator.generateWeb(100, 100, 10, 15, 20, 15);
		dynamicAlgorithm = new LPA();
		informedAlgorithm = dynamicAlgorithm;
		algorithm = informedAlgorithm;
		algorithm.setGraph(graph);
		loadEnvironment();
	}
	
	public void step() {
		algorithm.step();
		
	}
	
	public String addGraph(final GraphPropertyVO graphPropertyVO) {
		final String name = loadGraph(graphPropertyVO.getUrl());
		graphNameMap.put(name, graphPropertyVO);
		environment.getGraphs().add(graphPropertyVO);
		saveEnvironment();
		return name;
	}
	
	private String loadGraph(final String path) {
		try {
			
			final FileInputStream fileInputStream = new FileInputStream(path); // TODO - tmp !!!
			final String xml = IOUtils.toString(fileInputStream, "UTF8");
			final XStream xStream = new XStream();
			xStream.alias("graph", GraphVO.class);
			xStream.alias("vertex", VertexVO.class);
			xStream.alias("edge", EdgeVO.class);
			final GraphVO graphVO = (GraphVO) xStream.fromXML(xml, new GraphVO());
			
			graph = new UndirectedSparseGraph<Vertex2D, Edge>();
			final Map<String, Vertex2D> vertexMap = new HashMap<String, Vertex2D>();
			for (final VertexVO vertexVO : graphVO.getVertices()) {
				final Vertex2D vertex2d = domainFactory.createVertex2d(vertexVO);
				graph.addVertex(vertex2d);
				vertexMap.put(vertexVO.getId(), vertex2d);
			}
			
			for (final EdgeVO edgeVO : graphVO.getEdges()) {
				final String fromVertexId = edgeVO.getFromVertexId();
				final String toVertexId = edgeVO.getToVertexId();
				final Vertex2D fromVertex = vertexMap.get(fromVertexId);
				final Vertex2D toVertex = vertexMap.get(toVertexId);
				final Edge edge = domainFactory.createEdge(edgeVO, fromVertex, toVertex);
				graph.addEdge(edge, fromVertex, toVertex);
			}
			
			return graphVO.getName();
			
		} catch (final Exception e) {
			throw new IllegalArgumentException(e);
			
		}
	}
	
	public ChangeGraphResponse changeGraph(final ChangeGraphRequest request) {
		final GraphPropertyVO graphPropertyVO = graphNameMap.get(request.getName());
		
		final String path = graphPropertyVO.getUrl();
		loadGraph(path);
		algorithm.setGraph(graph);
		return new ChangeGraphResponse();
	}
	
	public ChangeHeuristicResponse changeHeuristic(
		final ChangeHeuristicRequest request)
	{
		final HeuristicPropertyVO heuristicPropertyVO = heuristicNameMap.get(request.getName());
		heuristic = DynamicFileClassLoader.loadInstance(heuristicPropertyVO);
		if (informedAlgorithm != null) {
			informedAlgorithm.setHeuristic(heuristic);
		}
		return new ChangeHeuristicResponse();
	}
	
	public ChangeAlgorithmResponse changeAlgorithm(
		final ChangeAlgorithmRequest request)
	{
		final AlgorithmPropertyVO algorithmPropertyVO = algorithNameMap.get(request.getName());
		algorithm = DynamicFileClassLoader.loadInstance(algorithmPropertyVO);
		if (algorithm instanceof InformedAlgorithm) {
			informedAlgorithm = (InformedAlgorithm<Vertex2D, Edge>) algorithm;
			informedAlgorithm.setHeuristic(heuristic);
		} else {
			informedAlgorithm = null;
		}
		if (algorithm instanceof DynamicAlgorithm) {
			dynamicAlgorithm = (DynamicAlgorithm<Vertex2D, Edge>) algorithm;
		} else {
			dynamicAlgorithm = null;
		}
		algorithm.setGraph(graph);
		return new ChangeAlgorithmResponse();
	}
	
	public Graph<Vertex2D, Edge> getGraph() {
		return graph;
	}
	
	public void saveGraph(final SaveGraphRequest request) {
		
		final GraphVO graphVO = new GraphVO();
		graphVO.setId(UUID.randomUUID().toString()); //TODO
		final Collection<Vertex2D> vertices = graph.getVertices();
		
		for (final Vertex2D vertex2d : vertices) {
			graphVO.getVertices().add(new VertexVO(vertex2d));
		}
		
		final Collection<Edge> edges = graph.getEdges();
		
		for (final Edge edge : edges) {
			graphVO.getEdges().add(new EdgeVO(edge));
		}
		
		final XStream xStream = new XStream();
		xStream.autodetectAnnotations(true);
		final String string = xStream.toXML(graphVO);
		try {
			final byte[] content = string.getBytes("UTF-8");
			final FileOutputStream dfis = new FileOutputStream(request.getFilePath());
			dfis.write(content);
			dfis.close();
		} catch (final Exception e) {
			e.printStackTrace();
		}
		
		final GraphPropertyVO graphPropertyVO = new GraphPropertyVO(request.getFilePath(), request.getName());
		environment.getGraphs().add(graphPropertyVO);
		graphNameMap.put(request.getName(), graphPropertyVO);
		saveEnvironment();
		
	}
	
	public void addAlgorithm(final AlgorithmPropertyVO algorithmPropertyVO) {
		final Algorithm<Vertex2D, Edge> instance = DynamicFileClassLoader
			.<Algorithm<Vertex2D, Edge>> loadInstance(algorithmPropertyVO);
		algorithNameMap.put(algorithmPropertyVO.getName(), algorithmPropertyVO);
		environment.getAlgorithms().add(algorithmPropertyVO);
		saveEnvironment();
		algorithm = instance;
		if (algorithm instanceof InformedAlgorithm<?, ?>) {
			informedAlgorithm = (InformedAlgorithm<Vertex2D, Edge>) instance;
		}
		
	}
	
	public void addHeuristic(final HeuristicPropertyVO heuristicPropertyVO) {
		final Heuristic<Vertex2D, Edge> instance = DynamicFileClassLoader.loadInstance(heuristicPropertyVO);
		heuristicNameMap.put(heuristicPropertyVO.getName(), heuristicPropertyVO);
		environment.getHeuristicPropertyVOs().add(heuristicPropertyVO);
		saveEnvironment();
		heuristic = instance;
	}
	
	public Collection<String> getAlgorithms() {
		return algorithNameMap.keySet();
	}
	
	public Collection<String> getHeuristic() {
		return heuristicNameMap.keySet();
	}
	
	public Collection<String> getGraphs() {
		return graphNameMap.keySet();
	}
	
	public void setToTarget(final Vertex2D vertex) {
		if (algorithm != null) {
			algorithm.setTarget(vertex);
		}
		
	}
	
	public void setToStart(final Vertex2D vertex) {
		if (algorithm != null) {
			algorithm.setStart(vertex);
		}
		
	}
	
	public Transformer<Edge, Paint> getEdgeColorTransformer() {
		if (algorithm != null) {
			return algorithm.getEdgeColorTransformer();
		} else {
			return new Transformer<Edge, Paint>() {
				
				public Color transform(final Edge input) {
					return Color.GRAY;
				}
			};
		}
	}
	
	public Transformer<Vertex2D, Paint> getVertexColorTransformer() {
		if (algorithm != null) {
			return algorithm.getVertexColorTransformer();
		} else {
			return new Transformer<Vertex2D, Paint>() {
				
				public Color transform(final Vertex2D input) {
					return Color.BLACK;
				}
			};
		}
	}
	
	public DomainFactory getDomainFactory() {
		return domainFactory;
	}
	
	public void deleteVertex(final Vertex2D vertex) {
		final Collection<Vertex2D> neighbors = graph.getNeighbors(vertex);
		for (final Vertex2D vertex2d : neighbors) {
			addChange(vertex2d);
		}
		graph.removeVertex(vertex);
		
	}
	
	public void addVertex(final Point2D location) {
		final Vertex2D newVertex = domainFactory.createVertex2d(location);
		graph.addVertex(newVertex);
		
	}
	
	public void deleteEdge(final Edge edge) {
		addChange(edge.getFromVertex());
		addChange(edge.getToVertex());
		graph.removeEdge(edge);
		
	}
	
	public void addEdge(final Vertex2D from, final Vertex2D to, final Double weight) {
		graph.addEdge(domainFactory.createEdge(from, to),
			from, to, EdgeType.UNDIRECTED);
		addChange(from);
		addChange(to);
		
	}
	
	private void addChange(final Vertex2D vertex2d) {
		if (dynamicAlgorithm != null) {
			dynamicAlgorithm.addChangedVertex(vertex2d);
		}
		else {
			algorithm.reset();
		}
	}
	
	public int getTime() {
		return algorithm.getTime();
	}
	
	public int getActualMemory() {
		return algorithm.getActualMemory();
	}
	
	public int getMaxMemory() {
		return algorithm.getMaxMemory();
	}
	
	public void newGraph() {
		graph = new UndirectedSparseGraph<Vertex2D, Edge>();
		algorithm.setGraph(graph);
		
	}
	
	public int getStepNumber() {
		return algorithm.getStepNumber();
	}
	
}
